/*
 *
 *  * Copyright (c) iwindplus Technologies Co., Ltd.2024-2030, All rights reserved.
 *
 *
 */

package com.iwindplus.base.http.client;

import cn.hutool.core.text.CharSequenceUtil;
import com.iwindplus.base.domain.constant.CommonConstant.ObservationConstant;
import com.iwindplus.base.http.client.domain.property.HttpClientProperty;
import com.iwindplus.base.http.client.domain.property.HttpClientProperty.Proxy;
import com.iwindplus.base.http.client.domain.property.HttpClientProperty.Retry;
import com.iwindplus.base.http.client.manager.HttpClientManager;
import io.micrometer.core.instrument.binder.httpcomponents.hc5.ObservationExecChainHandler;
import io.micrometer.observation.ObservationRegistry;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.hc.client5.http.auth.AuthScope;
import org.apache.hc.client5.http.auth.Credentials;
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.ChainElement;
import org.apache.hc.client5.http.impl.DefaultHttpRequestRetryStrategy;
import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

/**
 * HttpClient配置.
 *
 * @author zengdegui
 * @since 2023/08/31 20:32
 */
@Slf4j
@Configuration
@EnableConfigurationProperties({HttpClientProperty.class})
@ConditionalOnProperty(prefix = "http.client", name = "enabled", havingValue = "true", matchIfMissing = true)
public class HttpClientConfiguration {

    @Resource
    private HttpClientProperty property;

    @Resource
    private ObservationRegistry observationRegistry;

    /**
     * 创建 PoolingHttpClientConnectionManager
     *
     * @return PoolingHttpClientConnectionManager
     */
    @Bean
    @ConditionalOnMissingBean({PoolingHttpClientConnectionManager.class})
    public PoolingHttpClientConnectionManager poolingHttpClientConnectionManager() {
        PoolingHttpClientConnectionManagerBuilder connectionManager = PoolingHttpClientConnectionManagerBuilder
            .create()
            .setMaxConnTotal(this.property.getPool().getMaxConnTotal())
            .setMaxConnPerRoute(this.property.getPool().getMaxConnPerRoute());
        final PoolingHttpClientConnectionManager build = connectionManager.build();
        log.info("PoolingHttpClientConnectionManager={}", build);
        return build;
    }

    /**
     * 创建 HttpClientBuilder.
     *
     * @param connectionManager 连接池管理器
     * @return HttpClientBuilder
     */
    @Bean
    public HttpClientBuilder httpClientBuilder(PoolingHttpClientConnectionManager connectionManager) {
        RequestConfig requestConfig = RequestConfig.custom()
            .setConnectionKeepAlive(TimeValue.of(this.property.getConnectionKeepAlive()))
            .setConnectionRequestTimeout(Timeout.of(this.property.getConnectionRequestTimeout()))
            .setRedirectsEnabled(this.property.getRedirectsEnabled())
            .setMaxRedirects(this.property.getMaxRedirects())
            .setCircularRedirectsAllowed(Boolean.FALSE)
            .setResponseTimeout(Timeout.of(this.property.getResponseTimeout()))
            .setContentCompressionEnabled(this.property.getEnabledCompression())
            .build();
        HttpClientBuilder httpClientBuilder = HttpClients.custom()
            .setDefaultRequestConfig(requestConfig)
            .setConnectionManager(connectionManager);

        // 观察
        if (Boolean.TRUE.equals(this.property.getEnabledObservation())) {
            httpClientBuilder.addExecInterceptorAfter(ChainElement.RETRY.name(), ObservationConstant.HTTP_OBSERVATION_NAME,
                new ObservationExecChainHandler(observationRegistry));
        }

        // 重试
        final Retry retry = this.property.getRetry();
        if (Boolean.TRUE.equals(retry.getEnabled())) {
            // 重试次数
            httpClientBuilder.setRetryStrategy(
                new DefaultHttpRequestRetryStrategy(retry.getMaxAttempts(), TimeValue.of(retry.getPeriod())));
        } else {
            httpClientBuilder.disableAutomaticRetries();
        }

        // 代理
        final Proxy proxy = this.property.getProxy();
        if (Boolean.TRUE.equals(proxy.getEnabled())) {
            HttpHost httpHost = new HttpHost(proxy.getHost(), proxy.getPort());
            httpClientBuilder.setProxy(httpHost);
            String username = proxy.getUsername();
            String password = proxy.getPassword();
            if (CharSequenceUtil.isAllNotBlank(username, username)) {
                AuthScope authscope = new AuthScope(httpHost);
                Credentials credentials = new UsernamePasswordCredentials(username, password.toCharArray());
                BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
                credentialsProvider.setCredentials(authscope, credentials);
                httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
            }
        }

        log.info("HttpClientBuilder={}", httpClientBuilder);
        return httpClientBuilder;
    }

    /**
     * 创建 CloseableHttpClient.
     *
     * @param builder builder
     * @return CloseableHttpClient
     */
    @Bean
    public CloseableHttpClient closeableHttpClient(HttpClientBuilder builder) {
        final CloseableHttpClient httpClient = builder.build();
        log.info("CloseableHttpClient={}", httpClient);
        return httpClient;
    }

    /**
     * 创建 HttpClientManager.
     *
     * @return HttpClientManager
     */
    @Primary
    @Bean
    @ConditionalOnBean(CloseableHttpClient.class)
    public HttpClientManager httpClientManager() {
        HttpClientManager httpClientManager = new HttpClientManager();
        log.info("HttpClientManager={}", httpClientManager);
        return httpClientManager;
    }
}
